﻿package org.hsl.haxe;
import org.hsl.haxe.ISignaler;
import org.hsl.haxe.ISignalerSlotListBridge;
/**
 * A common implementation of the ISignaler interface.
 */
class CommonSignaler<D> implements ISignaler<D> {
	public var advanced(default, null):ISignalerAdvanced<D>;
	public var bubblingTarget(default, setBubblingTarget):ISignaler<D>;
	public var dispatcher(default, null):IDispatcher;
	private var signalerSlotListBridge:ISignalerSlotListBridge<D>;
	/**
	 * Creates a new common signaler. The passed dispatcher will be stored as the dispatcher property.
	 */
	public function new(dispatcher:IDispatcher):Void {
		// If dispatcher is null, throw an exception. Having null for a dispatcher might produce null object reference errors later
		// on: when the listeners use the produced signals.
		if (dispatcher == null) {
			// TODO: Throw a more (haXe library) or more-ish exception instead of this lame one.
			throw "The dispatcher argument must be non-null.";
		}
		// Setup the advanced API.
		advanced = new CommonSignalerAdvanced<D>(this);
		// Set the bubbling target to null, the setter will store a null object implementation instead of actually storing null.
		bubblingTarget = null;
		// Store the passed dispatcher.
		this.dispatcher = dispatcher;
		// Set the signaler-slot list bridge to a null object implementation, so dispatching signals before connecting to a slot
		// list will not produce null object reference errors.
		disconnectFromSlotList();
	}
	public inline function connectToSlotList(signalerSlotListBridge:ISignalerSlotListBridge<D>):Void {
		// If the passed signaler-slot list bridge is null, call the disconnectFromSlotList method which stores a null object
		// implementation. This will ensure that the signaler-slot list bridge is never null, and therefore there will be no null
		// object reference errors when trying to dispatch a signal.
		if (signalerSlotListBridge == null) {
			disconnectFromSlotList();
		} else {
			this.signalerSlotListBridge = signalerSlotListBridge;
		}
	}
	public inline function disconnectFromSlotList():Void {
		signalerSlotListBridge = new NullSignalerSlotListBridge<D>();
	}
	public function dispatch(?data:D):Void {
		// Send the signal to the connected slot list.
		signalerSlotListBridge.callSlots(new Signal<D>(data, dispatcher, dispatcher));
		// Bubble the signal.
		bubblingTarget.advanced.dispatchBubbling(data, dispatcher);
	}
	public inline function dispatchBubbling(data:D, initialDispatcher:IDispatcher):Void {
		// Send the signal to the connected slot list.
		signalerSlotListBridge.callSlots(new Signal<D>(data, dispatcher, initialDispatcher));
		// Bubble the signal.
		bubblingTarget.advanced.dispatchBubbling(data, initialDispatcher);
	}
	private function setBubblingTarget(value:ISignaler<D>):ISignaler<D> {
		// If value is null, use a null object implementation instead. This will ensure that the bubbling target is never null, and
		// therefore there will be no null object reference errors when trying to bubble a signal.
		if (value == null) {
			return bubblingTarget = new NullSignaler<D>();
		}
		// Check for recursive bubbling paths. If a recursive bubbling path is made, throw an exception. Recursive bubbling paths
		// will most likely cause malfunction on dispatching.
		var signaler:ISignaler<D> = value.bubblingTarget;
		while (signaler != null) {
			if (signaler == this) {
				throw "The bubblingTarget property of a signaler was set to a value that would result in recursive bubbling.";
			}
			signaler = signaler.bubblingTarget;
		}
		return bubblingTarget = value;
	}
	private function toString():String {
		return "[ISignaler]";
	}
}
/**
 * The ISignalerAdvanced implementation used by the common signaler.
 */
class CommonSignalerAdvanced<D> implements ISignalerAdvanced<D> {
	private var signaler:CommonSignaler<D>;
	/**
	 * Creates a new common signaler advanced.
	 */
	public function new(signaler:CommonSignaler<D>):Void {
		this.signaler = signaler;
	}
	public function connectToSlotList(signalerSlotListBridge:ISignalerSlotListBridge<D>):Void {
		// Route the call to the signaler provided on creation.
		signaler.connectToSlotList(signalerSlotListBridge);
	}
	public function disconnectFromSlotList():Void {
		// Route the call to the signaler provided on creation.
		signaler.disconnectFromSlotList();
	}
	public function dispatchBubbling(data:D, initialDispatcher:IDispatcher):Void {
		// Route the call to the signaler provided on creation.
		signaler.dispatchBubbling(data, initialDispatcher);
	}
	private function toString():String {
		return "[ISignalerAdvanced]";
	}
}